/**
 * File: FastSequenceIndex.java
 * Created by: mhaimel
 * Created on: Jun 23, 2011
 * CVS:  $Id: FastSequenceIndex.java 1.0 Jun 23, 2011 4:27:07 PM mhaimel Exp $
 */
package uk.ac.ebi.curtain.storage.sequence.fast;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicInteger;

import uk.ac.ebi.curtain.storage.sequence.InputStreamProvider;
import uk.ac.ebi.curtain.storage.sequence.StreamProvider;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;

/**
 * @author mhaimel
 *
 */
public class FastSequenceIndex {
	
	private static final long[] EMPTY_ARRAY = new long[0];
	private int blockSize = -1;
	private final AtomicInteger tidCnt = new AtomicInteger(0);
	private final ConcurrentNavigableMap<Long, Integer> id2tid = new ConcurrentSkipListMap<Long, Integer>();
	private final ConcurrentSkipListMap<Integer, long[]> cBlocksMap = new ConcurrentSkipListMap<Integer, long[]>();
	private final StreamProvider<InputStream> inProvider;
	
	public FastSequenceIndex(StreamProvider<InputStream> inProvider) {
		this.inProvider = inProvider;
	}
	
	public Collection<Long> getIdentifiers(){
		return new ArrayList<Long>(this.id2tid.keySet());
	}
	
	public void loadIndex() throws IOException{
		DataInputStream in = null;
		try{
			in = new DataInputStream(new BufferedInputStream(inProvider.openAndGetStream()));
			this.blockSize = in.readInt();
			if(in.readLong()!=0 || in.readLong() != 0){
				throw new IllegalStateException("Uncompressed and compressed start should be 0!!");
			}
			long prevUpos = 0;
			long prevCpos = 0;
			long id = -1;
			long upos = 0;
			while(true){
				id = in.readLong();
				if(id < 0){
					break;// END
				}
				upos = in.readLong();
				int bcnt = this.calculateBlocks(prevUpos,upos);
				long[] arr = new long[bcnt+1];
				arr[0] = prevCpos;
				for(int i = 1; i <= bcnt; ++i){
					arr[i] = in.readLong();
				}
				this.add(id, upos, arr);
				prevCpos = arr[bcnt];
				prevUpos = upos;
			}
			in.close();
			in = null;
		} catch (IOException e) {
			throw new IOException("Problems reading " + this.inProvider.getName(),e);
		}finally{
			FileIO.closeQuietly(in);
		}
	}
	
	public void add(Long id, Long uEnd, long[] cBlocks){
		Integer tid = _translateOrAddId(id);
		_addBlock(cBlocks, tid);
	}

	private void _addBlock(long[] cBlocks, Integer tid) {
		cBlocksMap.put(tid, cBlocks);
	}

	private Integer _translateOrAddId(Long id) {
		Integer tid = id2tid.get(id);
		if(null == tid){
			tid = tidCnt.getAndIncrement();
			id2tid.put(id, tid);
		}
		return tid;
	}
	
	private Integer _translateId(Long id) {
		return id2tid.get(id);
	}
	
	public long[] getAllCompressedBlocks(){
		NavigableMap<Long, long[]> bMap = new TreeMap<Long, long[]>(); 
		int total = 1;
		for(long[] barr : cBlocksMap.values()){
			bMap.put(barr[0], barr);
			total += barr.length-1;
		}
		long[] tmp = new long[total];
		int curr = 0;
		for(Entry<Long, long[]> s : bMap.entrySet()){
			long[] a = s.getValue();
			int len = a.length;
			if(curr == 0){
				System.arraycopy(a, 0, tmp, curr, len);
				curr = len;
			} else if(tmp[curr-1] == a[0]) { // end == start position
				System.arraycopy(a, 1, tmp, curr, len-1);
				curr += len-1;
			} else {
				throw new IllegalStateException("Gap in File!!! " + tmp[tmp.length-1] + " != " + a[0]);
			}
		}
		return tmp;
	}
	
	public long[] getCompressedBlocks(Long id){
		Integer tid = _translateId(id);
		if(null == tid){
			return EMPTY_ARRAY;
		}
		return _getBlocks(tid);
	}

	private long[] _getBlocks(Integer id) {
		return this.cBlocksMap.get(id);
	}
	
	public int calculateBlocks(long prevStart,long end){
		return calculateBlocks(end-prevStart);
	}
	
	public int getBlockSize() {
		return blockSize;
	}
	
	public int calculateBlocks(long size){
		int cnt = (int) (size/this.blockSize);
		if((size - (cnt*this.blockSize))>0){
			++cnt;
		}
		return cnt;
	}
	
}
